reactrouter.js ➔ instrumentReactRouter   D
last analyzed

Complexity

Conditions 12

Size

Total Lines 68
Code Lines 50

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 50
dl 0
loc 68
c 0
b 0
f 0
rs 4.8
cc 12

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

Complexity

Complex classes like reactrouter.js ➔ instrumentReactRouter often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

1
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
2
3
const browser = require('@sentry/browser');
4
const core = require('@sentry/core');
5
const React = require('react');
6
const hoistNonReactStatics = require('./hoist-non-react-statics.js');
7
8
// We need to disable eslint no-explicit-any because any is required for the
9
// react-router typings.
10
11
/**
12
 * A browser tracing integration that uses React Router v4 to instrument navigations.
13
 * Expects `history` (and optionally `routes` and `matchPath`) to be passed as options.
14
 */
15
function reactRouterV4BrowserTracingIntegration(
16
  options,
17
) {
18
  const integration = browser.browserTracingIntegration({
19
    ...options,
20
    instrumentPageLoad: false,
21
    instrumentNavigation: false,
22
  });
23
24
  const { history, routes, matchPath, instrumentPageLoad = true, instrumentNavigation = true } = options;
25
26
  return {
27
    ...integration,
28
    afterAllSetup(client) {
29
      integration.afterAllSetup(client);
30
31
      instrumentReactRouter(
32
        client,
33
        instrumentPageLoad,
34
        instrumentNavigation,
35
        history,
36
        'reactrouter_v4',
37
        routes,
38
        matchPath,
39
      );
40
    },
41
  };
42
}
43
44
/**
45
 * A browser tracing integration that uses React Router v5 to instrument navigations.
46
 * Expects `history` (and optionally `routes` and `matchPath`) to be passed as options.
47
 */
48
function reactRouterV5BrowserTracingIntegration(
49
  options,
50
) {
51
  const integration = browser.browserTracingIntegration({
52
    ...options,
53
    instrumentPageLoad: false,
54
    instrumentNavigation: false,
55
  });
56
57
  const { history, routes, matchPath, instrumentPageLoad = true, instrumentNavigation = true } = options;
58
59
  return {
60
    ...integration,
61
    afterAllSetup(client) {
62
      integration.afterAllSetup(client);
63
64
      instrumentReactRouter(
65
        client,
66
        instrumentPageLoad,
67
        instrumentNavigation,
68
        history,
69
        'reactrouter_v5',
70
        routes,
71
        matchPath,
72
      );
73
    },
74
  };
75
}
76
77
function instrumentReactRouter(
78
  client,
79
  instrumentPageLoad,
80
  instrumentNavigation,
81
  history,
82
  instrumentationName,
83
  allRoutes = [],
84
  matchPath,
85
) {
86
  function getInitPathName() {
87
    if (history.location) {
88
      return history.location.pathname;
89
    }
90
91
    if (browser.WINDOW.location) {
92
      return browser.WINDOW.location.pathname;
93
    }
94
95
    return undefined;
96
  }
97
98
  /**
99
   * Normalizes a transaction name. Returns the new name as well as the
100
   * source of the transaction.
101
   *
102
   * @param pathname The initial pathname we normalize
103
   */
104
  function normalizeTransactionName(pathname) {
105
    if (allRoutes.length === 0 || !matchPath) {
106
      return [pathname, 'url'];
107
    }
108
109
    const branches = matchRoutes(allRoutes, pathname, matchPath);
110
    for (const branch of branches) {
111
      if (branch.match.isExact) {
112
        return [branch.match.path, 'route'];
113
      }
114
    }
115
116
    return [pathname, 'url'];
117
  }
118
119
  if (instrumentPageLoad) {
120
    const initPathName = getInitPathName();
121
    if (initPathName) {
122
      const [name, source] = normalizeTransactionName(initPathName);
123
      browser.startBrowserTracingPageLoadSpan(client, {
124
        name,
125
        attributes: {
126
          [core.SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'pageload',
127
          [core.SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: `auto.pageload.react.${instrumentationName}`,
128
          [core.SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: source,
129
        },
130
      });
131
    }
132
  }
133
134
  if (instrumentNavigation && history.listen) {
135
    history.listen((location, action) => {
136
      if (action && (action === 'PUSH' || action === 'POP')) {
137
        const [name, source] = normalizeTransactionName(location.pathname);
138
        browser.startBrowserTracingNavigationSpan(client, {
139
          name,
140
          attributes: {
141
            [core.SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'navigation',
142
            [core.SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: `auto.navigation.react.${instrumentationName}`,
143
            [core.SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: source,
144
          },
145
        });
146
      }
147
    });
148
  }
149
}
150
151
/**
152
 * Matches a set of routes to a pathname
153
 * Based on implementation from
154
 */
155
function matchRoutes(
156
  routes,
157
  pathname,
158
  matchPath,
159
  branch = [],
160
) {
161
  routes.some(route => {
162
    const match = route.path
163
      ? matchPath(pathname, route)
164
      : branch.length
165
        ? // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
166
          branch[branch.length - 1].match // use parent match
167
        : computeRootMatch(pathname); // use default "root" match
168
169
    if (match) {
170
      branch.push({ route, match });
171
172
      if (route.routes) {
173
        matchRoutes(route.routes, pathname, matchPath, branch);
174
      }
175
    }
176
177
    return !!match;
178
  });
179
180
  return branch;
181
}
182
183
function computeRootMatch(pathname) {
184
  return { path: '/', url: '/', params: {}, isExact: pathname === '/' };
185
}
186
187
/* eslint-disable @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-member-access */
188
function withSentryRouting(Route) {
189
  const componentDisplayName = Route.displayName || Route.name;
190
191
  const WrappedRoute = (props) => {
192
    if (props?.computedMatch?.isExact) {
193
      const route = props.computedMatch.path;
194
      const activeRootSpan = getActiveRootSpan();
195
196
      core.getCurrentScope().setTransactionName(route);
197
198
      if (activeRootSpan) {
199
        activeRootSpan.updateName(route);
200
        activeRootSpan.setAttribute(core.SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, 'route');
201
      }
202
    }
203
204
    // @ts-expect-error Setting more specific React Component typing for `R` generic above
205
    // will break advanced type inference done by react router params:
206
    // https://github.com/DefinitelyTyped/DefinitelyTyped/blob/13dc4235c069e25fe7ee16e11f529d909f9f3ff8/types/react-router/index.d.ts#L154-L164
207
    return React.createElement(Route, { ...props,} );
208
  };
209
210
  WrappedRoute.displayName = `sentryRoute(${componentDisplayName})`;
211
  hoistNonReactStatics.hoistNonReactStatics(WrappedRoute, Route);
212
  // @ts-expect-error Setting more specific React Component typing for `R` generic above
213
  // will break advanced type inference done by react router params:
214
  // https://github.com/DefinitelyTyped/DefinitelyTyped/blob/13dc4235c069e25fe7ee16e11f529d909f9f3ff8/types/react-router/index.d.ts#L154-L164
215
  return WrappedRoute;
216
}
217
/* eslint-enable @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-member-access */
218
219
function getActiveRootSpan() {
220
  const span = core.getActiveSpan();
221
  const rootSpan = span && core.getRootSpan(span);
222
223
  if (!rootSpan) {
224
    return undefined;
225
  }
226
227
  const op = core.spanToJSON(rootSpan).op;
228
229
  // Only use this root span if it is a pageload or navigation span
230
  return op === 'navigation' || op === 'pageload' ? rootSpan : undefined;
231
}
232
233
exports.reactRouterV4BrowserTracingIntegration = reactRouterV4BrowserTracingIntegration;
234
exports.reactRouterV5BrowserTracingIntegration = reactRouterV5BrowserTracingIntegration;
235
exports.withSentryRouting = withSentryRouting;
236
//# sourceMappingURL=reactrouter.js.map
237